Applications usually plan on pushing graphics past their limits. If the rendering traversal is part of the application, then this traversal must be optimized so that it keeps the graphics subsystem busy. On a multiprocessing system, other operations for scene management formatting of data can be moved out of the renderer and into other processes, preferably running on other CPUs. Finally, a key part of real-time rendering is load management, providing a graceful response to overloading the graphics subsystem, which is discussed later in Section 8.
Efficient Coding
There is no escape from writing efficient code in the renderer. Immediate mode drawing loops are the most important parts since code in those loops are executed thousands of times per frame. For peak performance from these loops, one should do the following:
- Minimize the loop overhead and decision logic in the polygon loops. Unroll per-vertex code and duplicate code, as opposed to using per-vertex if-tests.
- Use a flat data structure for the draw traversal - you want to minimize the number of memory pages you need to touch in a given loop.
- Disassemble code to examine loop overhead (and to check that the compiler is doing what you expect).
Display list rendering requires less optimization because it does not require the tight loops for rendering individual polygons. However, this is at the cost of more memory usage for storing the display list and less flexibility in being able to edit the geometry in the list for dynamic objects. The extra memory required by display lists can be quite significant because there can be no vertex sharing in display lists. This can restrict the number of objects you can hold in memory and will also slow the time to page in new objects if the graphics display lists must be re-created. Additionally, display lists may need to be of a certain minimum size to be handled efficiently by the system. If there are many small moving objects in the scene, the result will be many small display lists. If you have the choice, given the option between immediate mode rendering and database paging, you might choose to use at least some immediate mode, particularly for dynamic objects.
Don't let the host be the bottleneck
IRIS PerformerTM, a Silicon Graphics toolkit for developing real-time graphics applications, uses a fairly aggressive technique for achieving high-performance immediate-mode rendering. Data structures for geometry enforce the use of efficient drawing primitives. Geometry is grouped into sets by type and attribute bindings (use of per-vertex or per-polygon colors, normals, and texture coordinates). For each combination of primitive and attribute binding, there is a specialized routine with a tight loop to draw the geometry in that set. The result is several hundred such routines but the use of macros makes the code easy to generate and maintain. IRIS Performer also provides an optimized display list mode that is actually an immediate mode display list and shares the application copy of data instead of copying off a separate, uneditable copy. This is discussed in [Rohlf94], and [PFPG94]. Host rendering optimization techniques 24are also discussed in detail in [GLPTT92].
Multiprocessing
Multiprocessing can be used to allow the renderer to devote its time issuing graphics calls while other tasks, such as scene and load management can be placed into other processes. There are several large tasks that are obvious candidates for such course-grained multiprocessing:
- the real-time application -- processes inputs from IO, calculates new viewing parameters, positional parameters for objects, and parameters for dynamic geometry,
- scene management -- culling out parts of the scene graph that are not in the viewing frustum, calculating LOD information, generating a display list for the rendering traversal,
- dynamic editing of geometric data,
- IO handling -- polling external devices, database paging,
- intersection traversals for collision detection,
- complex simulations for various vehicles.
A combination of pipelining and parallelism can be used to get the right throughput/latency trade-off for your application and the target machine. IRIS PerformerTM provides a process pipeline:
FIGURE 12. IRIS Performer Process Pipeline
This process pipeline, described in [Rohlf94], is re-configurable to allow:
- a pipeline where the cull and draw are parallel processes (app->cull/draw),
- a model where the cull and draw are performed by a single process that culls and renders simultaneously (app->cull_draw),
- a minimal-latency model where all tasks are performed by a single process (app_cull_draw).
Multiprocessing also allows additional tasks to be done that will make the rendering task more efficient, such as:
- generating a per-frame, optimized display list for the rendering task so that the drawing traversal does not need to traverse the original database,
- sorting geometry by mode to minimize mode changes,
- host backface removal (and removal of backfaced objects) to save additional host bandwidth,
- flattening of dynamic transformations over objects of only one or two polygons.
It is important to identify which tasks must be real-time, and which can run asynchronously and extend beyond frame boundaries. Real-time tasks are those that must happen within a fixed interval of time, and severe consequences will result if the task extends beyond its frame. The main application, cull, and draw tasks are all real-time tasks. However, it might not be so traumatic if, for example, some of the collision results are a frame late. The polling of external control devices should probably be done in a separate, asynchronous process -- if those results are late, extrapolation from previous results is probably better than waiting. Real-time tasks are discussed further in Section 8.
A real-time process should not poll an external device.